<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace Think\Cache\Driver;
use Think\Cache;
defined('THINK_PATH') or exit();

/**
 * Redis缓存驱动
 * 要求安装phpredis扩展：https://github.com/nicolasff/phpredis
 */
class Redis extends Cache {
    /**
     * 架构函数
     * @param array $options 缓存参数
     * @access public
     */
    public function __construct($options=array()) {
        if ( !extension_loaded('redis') ) {
            E(L('_NOT_SUPPORT_').':redis');
        }
        $options = array_merge(array (
            'host'          => C('REDIS_HOST') ? : '127.0.0.1',
            'port'          => C('REDIS_PORT') ? : 6379,
            'timeout'       => C('REDIS_TIMEOUT') ? : false,
            'auth'          => C('REDIS_AUTH') ? : '',
            'persistent'    => false,
            'db_number'     => C('REDIS_DEFAULT_NUMBER') ? : 0,
        ),$options);

        $this->options =  $options;
        $this->options['expire'] =  isset($options['expire'])?  $options['expire']  :   C('DATA_CACHE_TIME');
        $this->options['prefix'] =  isset($options['prefix'])?  $options['prefix']  :   C('DATA_CACHE_PREFIX');
        $this->options['auth'] =  isset($options['auth'])?  $options['auth']  :   C('REDIS_AUTH');
        $this->options['length'] =  isset($options['length'])?  $options['length']  :   0;
        $this->options['db_number'] =  isset($options['db_number'])?  $options['db_number']  :  C('REDIS_DEFAULT_NUMBER');
        $func = $options['persistent'] ? 'pconnect' : 'connect';
        $this->handler  = new \Redis;
        $options['timeout'] === false ?
            $this->handler->$func($options['host'], $options['port']) :
            $this->handler->$func($options['host'], $options['port'], $options['timeout']);
        if($this->options['auth']){
            //2016-07-05 陈聪:原始类文件不包含auth认证
            $this->handler->auth($this->options['auth']);
        }
        $this->handler->select($this->options['db_number']);
    }

    //@todo String（字符串）

    /**
     * 读取缓存
     * @access public
     * @param string $name 缓存变量名
     * @return mixed
     */
    public function get($name) {
        N('cache_read',1);
        $value = $this->handler->get($this->options['prefix'].$name);
        $jsonData  = json_decode( $value, true );
        return ($jsonData === NULL) ? $value : $jsonData;	//检测是否为JSON数据 true 返回JSON解析数组, false返回源数据
    }

    /**
     * 写入缓存
     * @access public
     * @param string $name 缓存变量名
     * @param mixed $value  存储数据
     * @param integer $expire  有效时间（秒）
     * @return boolean
     */
    public function set($name, $value, $expire = null) {
        N('cache_write',1);
        if(is_null($expire)) {
            $expire  =  $this->options['expire'];
        }
        $name   =   $this->options['prefix'].$name;
        //对数组/对象数据进行缓存处理，保证数据完整性
        $value  =  (is_object($value) || is_array($value)) ? json_encode($value) : $value;
        if(is_int($expire) && $expire) {
            $result = $this->handler->setex($name, $expire, $value);
        }else{
            $result = $this->handler->set($name, $value);
        }
        if($result && $this->options['length']>0) {
            // 记录缓存队列
            $this->queue($name);
        }
        return $result;
    }


    //@todo Server（服务器）

    /**
     * 清除缓存
     * @access public
     * @return boolean
     */
    public function clear() {
        return $this->handler->flushDB();
    }

    //@todo Connection（连接）

    /**
     * 设置Redis连接库
     * @param int $db_number 库序号,默认0
     */
    public function select($db_number=0){
        return $this->handler->select($db_number);
    }

    //@todo Key（键）

    /**
     * 删除缓存
     * @access public
     * @param string $name 缓存变量名
     * @return boolean
     */
    public function rm($name) {
        return $this->handler->delete($this->options['prefix'].$name);
    }

    /**
     * @desc 检查给定 key 是否存在。
     * @param $key
     * @return bool|string
     */
    public function exists($key){
        return $this->handler->exists($this->options['prefix'] . $key);
    }

    /**
     * 搜索Keys
     * @param $keys
     * @return mixed
     */
    public function keys($key){
        return $this->handler->keys($this->options['prefix'] . $key);
    }

    //@todo List（列表）

    /**
     * @desc 构建一个列表(先进后去，类似栈)
     * @param $key
     * @param $value
     * @return int
     */
    public function lpush($key, $value){
        if (is_array($value)) {
            $value = serialize($value);
        }
        return $this->handler->lpush($this->options['prefix'] . $key, $value);
    }

    /**
     * @desc 移除并返回列表 key 的头元素
     * @author 1503-陈聪
     */
    public function lpop($key, $serialize = false){
        $value = $this->handler->lpop($this->options['prefix'] . $key);
        if ($serialize === true && $value != false) {
            $value = unserialize($value);
        }
        return $value;
    }

    /**
     * @desc 返回列表 key 的长度。
     * @author 1503-陈聪
     */
    public function llen($key){
        return $this->handler->llen($this->options['prefix'] . $key);
    }

    /**
     * @desc 构建一个列表(先进先去，类似队列)
     * @param $key $key KEY名称
     * @param $value $value 值
     * @return int
     */
    public function rpush($key, $value){
        if (is_array($value)) {
            $value = serialize($value);
        }
        return $this->handler->rpush($this->options['prefix'] . $key, $value);
    }

    /**
     * @desc 获取所有列表数据（从头到尾取）
     * @param $key KEY名称
     * @param $head 开始
     * @param $tail 结束
     * @return array
     */
    public function lranges($key, $head, $tail, $serialize = false){
        $rs=$this->handler->lrange($this->options['prefix'] . $key, $head, $tail);
        $newArr=array();
        foreach($rs AS $k=>$value){
            if ($serialize === true && $value != false) {
                $value = unserialize($value);
            }
            $newArr[$k]=$value;
        }
        return $newArr;
    }

    //@todo Hash（哈希表）

    /**
     * @desc @desc 写入,如果值是数组则进行序列化
     * @author 陈聪
     */
    public function hset($name, $key, $value){
        if (is_array($value)) {
            $value = serialize($value);
        }
        return $this->handler->hset($this->options['prefix'] . $name, $key, $value);
    }

    /**
     * @desc 读取,对序列化字符串进行反序列化
     * @author 陈聪
     */
    public function hget($name, $key, $serialize = false){
        $row = $this->handler->hget($this->options['prefix'] . $name, $key);
        if ($serialize === true && $row != false) {
            $row = unserialize($row);
        }
        return $row;
    }

    /**
     * @desc 删除哈希表 key 中的一个指定域，不存在的域将被忽略。
     */
    public function hdel($name, $key){
        return $this->handler->hdel($this->options['prefix'] . $name, $key);
    }

    /**
     * @desc 获取所有序列化数据
     */
    public function hgetAll($name, $serialize = false){
        $rs=$this->handler->hgetAll($this->options['prefix'] . $name);
        $newArr=array();
        foreach($rs AS $k=>$value){
            if ($serialize === true && $value != false) {
                $value = unserialize($value);
            }
            $newArr[$k]=$value;
        }
        return $newArr;
    }

    /**
     * @desc 同时将多个 field-value (域-值)对设置到哈希表 key 中。
     *       此命令会覆盖哈希表中已存在的域。
     *       如果 key 不存在，一个空哈希表被创建并执行 HMSET 操作
     */
    public function hmset($key, $field, $timeOut = 0){
        $key = $this->options['prefix'] . $key;
        if ($timeOut > 0) {
            $retRes = $this->handler->expire($key, $timeOut);
        }
        return $this->handler->hmset($key, $field);
    }

    /**
     * @desc 返回哈希表 key 中域的数量
     */
    public function hlen($key){
        return $this->handler->hlen($this->options['prefix'] . $key);
    }

    //@todo SortedSet（有序集合）

    /**
     * @desc 将一个member元素及其 score 值加入到有序集 key 当中。
     *       如果某个 member 已经是有序集的成员，那么更新这个 member 的 score 值，并通过重新插入这个 member 元素，来保证该 member 在正确的位置上。
     *       score 值可以是整数值或双精度浮点数。
     *       如果 key 不存在，则创建一个空的有序集并执行 ZADD 操作。
     *       当 key 存在但不是有序集类型时，返回一个错误。
     *       对有序集的更多介绍请参见 sorted set 。
     */
    public function zadd($key, $score, $member){
        return $this->handler->zadd($this->options['prefix'] . $key, $score, $member);
    }

    /**
     * @desc 返回有序集 key 的基数。
     */
    public function zcard($key){
        return $this->handler->zcard($this->options['prefix'] . $key);
    }

    /**
     * @desc 返回有序集 key 中， score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
     *       关于参数 min 和 max 的详细使用方法，请参考 ZRANGEBYSCORE 命令。
     */
    public function zcount($key, $min, $max){
        return $this->handler->zcount($this->options['prefix'] . $key, $min, $max);
    }

    /**
     * @desc 返回有序集 key 中，成员 member 的 score 值。
     *       如果 member 元素不是有序集 key 的成员，或 key 不存在，返回 nil 。
     */
    public function zscore($key, $member){
        return $this->handler->zscore($this->options['prefix'] . $key, $member);
    }

    /**
     * @desc 为有序集 key 的成员 member 的 score 值加上增量 increment 。
     *       可以通过传递一个负数值 increment ，让 score 减去相应的值，比如 ZINCRBY key -5 member ，就是让 member 的 score 值减去 5 。
     *       当 key 不存在，或 member 不是 key 的成员时， ZINCRBY key increment member 等同于 ZADD key increment member 。
     *       当 key 不是有序集类型时，返回一个错误。
     *       score 值可以是整数值或双精度浮点数。
     */
    public function zincrby($key, $increment, $member){
        return $this->handler->zincrby($this->options['prefix'] . $key, $increment, $member);
    }

    /**
     * @desc 返回有序集 key 中，指定区间内的成员。
     *       其中成员的位置按 score 值递增(从小到大)来排序。
     *       具有相同 score 值的成员按字典序(lexicographical order )来排列。
     *       如果你需要成员按 score 值递减(从大到小)来排列，请使用 ZREVRANGE 命令。
     *       下标参数 start 和 stop 都以 0 为底，也就是说，以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推。
     *       你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推。
     *       超出范围的下标并不会引起错误。
     *       比如说，当 start 的值比有序集的最大下标还要大，或是 start > stop 时， ZRANGE 命令只是简单地返回一个空列表。
     *       另一方面，假如 stop 参数的值比有序集的最大下标还要大，那么 Redis 将 stop 当作最大下标来处理。
     *       可以通过使用 WITHSCORES 选项，来让成员和它的 score 值一并返回，返回列表以 value1,score1, ..., valueN,scoreN 的格式表示。
     *       客户端库可能会返回一些更复杂的数据类型，比如数组、元组等。
     */
    public function zrange($key, $start, $stop, $withscores = false){
        return $this->handler->zrange($this->options['prefix'] . $key, $start, $stop, $withscores);
    }

    /**
     * @desc 返回有序集 key 中，指定区间内的成员。
     *       其中成员的位置按 score 值递减(从大到小)来排列。
     *       具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
     *       除了成员按 score 值递减的次序排列这一点外， ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
     */
    public function zrevrange($key, $start, $stop, $withscores = false){
        return $this->handler->zrevrange($this->options['prefix'] . $key, $start, $stop, $withscores);
    }

    /**
     * @desc 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
     *       具有相同 score 值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的，不需要额外的计算)。
     *       可选的 LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count )，注意当 offset 很大时，定位 offset 的操作可能需要遍历整个有序集，此过程最坏复杂度为 O(N) 时间。
     *       可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员，还是将有序集成员及其 score 值一起返回。
     *       该选项自 Redis 2.0 版本起可用。
     *       区间及无限
     *       min 和 max 可以是 -inf 和 +inf ，这样一来，你就可以在不知道有序集的最低和最高 score 值的情况下，使用 ZRANGEBYSCORE 这类命令。
     *       默认情况下，区间的取值使用闭区间 (小于等于或大于等于)，你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
     */
    public function zRangeByScore($key, $min, $max, $options = array()){
        return $this->handler->zRangeByScore($this->options['prefix'] . $key, $min, $max, $options);
    }

    /**
     * @desc 返回有序集 key 中， score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。有序集成员按 score 值递减(从大到小)的次序排列。
     *       具有相同 score 值的成员按字典序的逆序(reverse lexicographical order )排列。
     *       除了成员按 score 值递减的次序排列这一点外， ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
     */
    public function zRevRangeByScore($key, $start, $end, $options = array()){
        return $this->handler->zRevRangeByScore($this->options['prefix'] . $key, $start, $end, $options);
    }

    /**
     * @desc 移除有序集 key 中的一个或多个成员，不存在的成员将被忽略。
     *       当 key 存在但不是有序集类型时，返回一个错误。
     * @param $key
     * @param $member
     */
    public function zRem($key, $member){
        return $this->handler->zRem($this->options['prefix'] . $key, $member);
    }

    //@todo Set（集合）

    /**
     * @desc 将一个或多个 member 元素加入到集合 key 当中，已经存在于集合的 member 元素将被忽略。
     *       假如 key 不存在，则创建一个只包含 member 元素作成员的集合。
     *       当 key 不是集合类型时，返回一个错误。
     */
    public function sadd($key, $score, $member){
        return $this->handler->sadd($this->options['prefix'] . $key, $score, $member);
    }

    /**
     * @desc 增加scan函数
     * @author 陈聪
     */
    public function scan(&$it,&$match,&$count){
        $rs=array();
        while ($keys = $this->handler->scan($it, $match, $count)) {
            foreach ($keys as $key) {
                $rs[] = $key;
            }
        }
        return $rs;
    }
}
